﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Net.Sockets;
using System.Net;
using System.Threading.Tasks;
using System.Threading;
using OpenServe.Net.Library.Extentions;
using Newtonsoft.Json;
using OpenServe.Net.Messaging;
using OpenServe.Net.Library.Interfaces;
using OpenServe.Net.Messaging.Server;
using OpenServe.Net.Library.Utils;
using System.Collections.Concurrent;
using System.Net.Security;
using System.Security.Authentication;
using System.Security.Cryptography.X509Certificates;
using System.Reflection;
using System.IO;
using Microsoft.Scripting.Hosting;
using Microsoft.Scripting;
using IronPython.Hosting;
using Autofac;

namespace OpenServe.Net.Server {
    public class Server {
        public class Client {
            private BlockingCollection<Message> queue = new BlockingCollection<Message>();
            private Dictionary<string, object> pVars = new Dictionary<string, object>();
            public Guid GUID { get; private set; }
            private ScriptScope scope = null;
            private ScriptEngine engine = Python.CreateEngine();
            public List<IMsgHandler> Handlers = new List<IMsgHandler>();
            private StringBuilder sb = new StringBuilder();
            public string Name { get; set; }
            public TcpClient TcpClient { get; set; }
            public dynamic Stream { get; set; }
            public Server Parent { get; set; }
            private CancellationTokenSource cts = new CancellationTokenSource();
            private ManualResetEvent readReset = new ManualResetEvent(false);
            private byte[] buffer = new byte[Utils.BufferSize];
            public byte[] Buffer {
                get { return buffer; }
                set { buffer = value; }
            }
            public void Send(Message msg) {
                queue.Add(msg);
            }
            public void Send(Header header, string msg) {
                header.Destination = header.Source;
                header.Source = new Guid();
                queue.Add(new Message { Header = header, Body = msg });
            }
            public Client() {
                GUID = Guid.NewGuid();
                // Scripting
                if (!Directory.Exists("Scripts")) Directory.CreateDirectory("Scripts");
                scope = engine.CreateScope();

                //Script Scope
                scope.SetVariable("env", "server");
                scope.SetVariable("vars", scriptVars);
                scope.SetVariable("svr", this);
                scope.SetVariable("pvars", pVars);

                Handlers.Add(new DefaultHandler(this));
                Task.Factory.StartNew(() => {
                    while (!cts.IsCancellationRequested) {
                        var msg = queue.Take(cts.Token);
                        if (cts.IsCancellationRequested) break;
                        var b = (msg.ToJson() + Utils.Delimiter).ToBytes();
                        Stream.Write(b, 0, b.Length);
                    }
                }, cts.Token);
            }
            ~Client() {
                cts.Cancel();
            }
            
            public void AddHandler(IMsgHandler handler) {
                var h = handler;
                h.Parent = this;
                Handlers.Add(h);
            }

            public void Receive() {
                try {
                    while (TcpClient.Connected) {
                        readReset.Reset();
                        var stream = Stream as Stream;
                        stream.BeginRead(this.Buffer, 0, Utils.BufferSize, (ar) => {
                            var read = Stream.EndRead(ar);
                            //var read = Stream.Read(b, 0, Utils.BufferSize);
                            sb.Append(Buffer.BToString());
                            if (read < Utils.BufferSize) {
                                var msgs = sb.ToString().Split(new[] { Utils.Delimiter }, StringSplitOptions.RemoveEmptyEntries).ToList();
                                msgs.ForEach(m => {
                                    var msg = new Message(m);
                                    Handlers.ForEach(h => Task.Factory.StartNew(hd => { var mh = hd as IMsgHandler; mh.HandleMessage(msg); }, h, cts.Token));
                                    // Run Scripts
                                    Directory.GetFiles("Scripts", "*.py").ToList().ForEach(script => {
                                        try {
                                            // Script Scope
                                            scope.SetVariable("msg", msg.Clone());
                                            var source = engine.CreateScriptSourceFromFile(script);
                                            source.Execute(scope);
                                        } catch (Exception ex) { 
                                            Console.WriteLine(script);
                                            //Console.WriteLine();
                                            //Console.WriteLine(File.ReadAllText(script));
                                            //Console.WriteLine();
                                            Console.WriteLine(ex.Message); }
                                    });
                                });
                                sb = new StringBuilder();
                            }
                            Buffer = new byte[Utils.BufferSize];
                            readReset.Set();
                        }, this);
                        readReset.WaitOne();
                    }
                } catch (Exception ex) { Console.WriteLine(ex.Message); Parent.Clients.Remove(this); }
            }
        }

        public List<IMsgHandler> Handlers = new List<IMsgHandler>();

        private X509Certificate certificate;
        private TcpListener listner;
        private CancellationTokenSource cts;
        private ManualResetEvent mre;
        public List<Client> Clients { get; set; }

        private static Dictionary<string, object> scriptVars = new Dictionary<string, object>();

        private bool _secured = false;
        public bool Secure {
            get { return _secured; }
            set { _secured = value; }
        }
        
        public Server(int port = 5000) {
            var _assembly = Assembly.GetAssembly(typeof(Server));

            var stream = _assembly.GetManifestResourceStream("OpenServe.Net.Server.OpenServe.pfx");
            var buffer = new byte[stream.Length];
            stream.Read(buffer, 0, (int)stream.Length);
            certificate = new X509Certificate2(buffer, "OpenServe.Net");

            Clients = new List<Client>();
            cts = new CancellationTokenSource();
            mre = new ManualResetEvent(false);
            listner = new TcpListener(IPAddress.Any, port);
        }

        public void AddHandler(IMsgHandler handler) {
            handler.Parent = this;
            Handlers.Add(handler);
        }

        public void Start() {
            Task.Factory.StartNew(() => {
                try {
                    listner.Start(50);
                    while (!cts.Token.IsCancellationRequested) {
                        mre.Reset();
                        listner.BeginAcceptTcpClient(ac => {
                            try {
                                var tc = (ac.AsyncState as TcpListener).EndAcceptTcpClient(ac);
                                var c = new Client { 
                                    TcpClient = tc, 
                                    Parent = this,
                                };
                                if (Secure) {
                                    c.Stream = new SslStream(tc.GetStream());
                                    c.Stream.ReadTimeout = 50;
                                    c.Stream.WriteTimeout = 50;
                                    try {
                                        c.Stream.AuthenticateAsServer(certificate, false, SslProtocols.Default, true);
                                    } catch { mre.Set(); return; }
                                } else c.Stream = tc.GetStream();

                                Handlers.ForEach(c.AddHandler);
                                Clients.Add(c);
                                mre.Set();
                                c.Receive();
                            } catch (Exception ex) {
                                Console.WriteLine(ex.Message);
                            }
                        }, listner);
                        mre.WaitOne();
                    }

                } catch (Exception ex) {
                    Console.WriteLine(ex.Message);
                }
            }, cts.Token);
        }

        public void Stop() {
            cts.Cancel();
            this.Clients = new List<Client>();
            listner.Stop();
        }
    }
}
